跳到主要内容

Java JPA 规范

JPA 是什么?

因为一开始接触的就是 MyBatis 所以对 JPA 这个词有点陌生。

JPA 全称为 Java Persistence API ,Java 持久化 API 是 Sun公司在java EE 5规范中提出的 Java持久化接口。JPA 吸取了目前 Java持久化技术的优点,旨在规范、简化 Java对象的持久化工作。使用 JPA 持久化对象,并不是依赖于某一个 ORM框架。

JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释(下面那个包);JPA 的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA 基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。

注意:侵入式和非侵入式的简单理解就是,在你的系统中去除掉某个【部分】,如果系统整体已经不能正常运行了,那么这个【部分】就是侵入式的;而如果系统整体仍然能够正常运行,那么这个【部分】就是非侵入式的。

它的接口都位于这个包下面

<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>

补充知识:侵入性与非侵入性

参考资料 侵入式和非侵入式框架的区别

什么是框架的侵入性?

一句话概括就是,你的代码需要依赖框架的代码,如果把框架拿掉或者换一个框架,就需要重新修改代码。

举个很简单的栗子啦:

如果使用侵入式的框架,一般需要继承或者实现框架的某一个类或接口,这样你把框架拿掉以后就会运行不起来,甚至在代码层面会报错哦。

那么如同 Spring 这种非侵入式的框架,都是利用反射和动态调用来实例化的,代码中没有任何与 Spring 交叉的类,哪怕把 Spring 的 jar 全部去了,你的代码不受任何影响,加上其他的框架后可以继续运行。

为什么要这么做呢?

代码有一个很好的设计原则是 “高内聚,低耦合” ,这个原则也使得必须降低框架的侵入性。

JDBC 与 JPA 的关系

JDBC(Java DataBase Connectivity)是 Java连接数据库操作的原生接口。JDBC 对 Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型。作为API,JDBC 为程序开发提供标准的接口,并为各个数据库厂商及第三方中间件厂商实现与数据库的连接提供了标准方法。

一句话概括:jdbc 是所有框架操作数据库的必须要用的,由数据库厂商提供,但是为了方便 java程序员调用各个数据库,各个数据库厂商都要实现 jdbc接口。

JPA(Java Persistence API)是 Java持久化规范,是 ORM(对象-关系映射-模型)框架的标准,主流 ORM 框架都实现了这个标准。Sun引入新的 JPA ORM 规范出于两个原因:

  • 其一,简化现有 Java EE和Java SE应用开发工作;
  • 其二,Sun希望整合ORM技术,实现天下归一。

总结:ORM是一种思想,是插入在应用程序与 JDBC API 之间的一个中间层,JDBC 并不能很好地支持面向对象的程序设计,ORM 解决了这个问题,通过 JDBC将字段高效的与对象进行映射。具体实现有 hibernate、spring data jpa、open jpa。

JPA、Hibernate、Spring Data JPA 的关系

网上常说的 JPA 其实是指 Spring Data JPA 这个框架

上面这张关系图展示了这三者的关系

  • JPA(Java Persistence API)是规范,它指明了持久化、读取和管理 Java 对象映射到数据库表时的规范。
  • Hibernate 则是一个 ORM 框架,它实现了 Java 对象到数据库表的映射。也就是说,Hibernate 提供了 JPA 的一种实现。
  • Spring Data JPA 是 Spring Framework 的一部分。它不是 JPA 的实现,而是在 JPA 之上提供更高层次的抽象,可以减少很多模板代码。而 Spring Data JAP 的默认实现是 Hibernate,当然也可以其他的 JPA Provider。

MyBatis 与 ORM 的关系

参考资料 MyBatis不是完整的ORM框架? - 章鱼猫的回答 - 知乎 参考资料 MyBatis不是完整的ORM框架? - 潜龙勿用的回答 - 知乎

Mybatis 不是完整的 ORM,或者说只是半自动的 ORM,而 Hibernate 才是全自动 ORM 框架,而 Mybatis 是半自动的。hibernate 完全可以通过对象关系模型实现对数据库的操作,拥有完整的 JavaBean 对象与数据库的映射结构来自动生成 SQL。而 Mybatis 仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写 SQL 来实现和管理。

而且 MyBatis 没有实现 JPA,他和 ORM 框架的设计思路完全不一样。

MyBatis 是拥抱 SQL,而 ORM 则更靠近面向对象,不建议写 SQL,实在要写推荐你写 HQL 代替。 Mybatis是 SQL Mapper 框架而不是 ORM 框架,当然 ORM 和 Mybatis 都是持久层框架。

JPA 中的元数据注释

参考资料 什么是JPA?Java Persistence API简介 参考资料 详解 @Entity 和 @Table 注解的用法 参考资料 阮一峰老师的 ORM 实例教程

ORM 把数据库映射成对象。

数据库的表(table)     --> 类(class)
记录(record,行数据) --> 对象(object)
字段(field) --> 对象的属性(attribute)

与大多数现代框架一样,JPA 遵循约定编码(也称为约定优于配置),其中框架提供基于行业最佳实践的默认配置。作为一个示例,名为 Musician 的类将默认映射到名为 Musician 的数据库表。

传统配置是节省时间的,并且在许多情况下它运行良好。也可以自定义 JPA配置。例如可以使用 JPA 的 @Table 注释来指定应该存储 Musician 类的表。

常用注解一览

参考资料 Spring Data JPA了解常用注解和属性

映射关系的注解

映射关系的属性

@Entity 实体注释

@Entity
public class Musician {
// ..class body
}

持久对象有时称为实体。附加 @Entity 到类,Musician 告知 JPA 应该保留此类及其对象。

@Entity 说明这个 class 是实体类,并且使用默认的 orm 规则,即 class 名即数据库表中表名,class 字段名即表中的字段名。

@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
private String name;
private int age;
private String addree;
// Getters and Setters
}

如果没有 @javax.persistence.Entity@javax.persistence.Id 这两个注解的话

它完全就是一个典型的 POJO 的 Java 类,现在加上这两个注解之后,就可以作为一个实体类与数据库中的表相对应。

他在数据库中的对应的表为:

@Table 注释

@Entity
@Table(name="musician")
public class Musician {
// ..class body
}

告诉 JPA 将实体(Musician类)持久化到 musician 表中

@Table 注解是一个非必须的注解。@Table 注解指定了 Entity 所要映射带数据库表,其中 @Table.name() 用来指定映射表的表名。

声明此对象映射到数据库的数据表,通过它可以为实体指定表(table)、目录(Catalog) 和 schema 的名字。

如果同时使用了 @Entity(name="student")@Table(name="students"),最终的对应的表名必须是哪个?

答案是 students,这说明优先级:@Table > @Entity

@Id 指定主键

@Entity
public class Musician {
@Id
private Long id;

// ..class body
}

在这种情况下,我们使用 JPA 的 @Id 注释将 id 字段指定为 Musician 主键。默认情况下,此配置假定主键将由数据库设置 例如,当字段设置为在表上自动递增时。

@GeneratedValue 生成策略

@GeneratedValue 设置主键生成策略,此方式依赖于具体的数据库

@Data
@Table(name="tb_brand")
public class Brand implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;//品牌id

/* .... */

}

@Column 对应字段名

@Column:表示属性所对应字段名进行个性化设置

@Data
@Table(name="tb_brand")
public class Brand implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;//品牌id

@Column(name = "name")
private String name;//品牌名称

@Column(name = "image")
private String image;//品牌图片地址

@Column(name = "letter")
private String letter;//品牌的首字母

@Column(name = "seq")
private Integer seq;//排序

}

@Transient 忽略字段

@Transient:表示属性并非数据库表字段的映射,ORM框架将忽略该属性,如果一个属性并非数据库表的字段映射,就务必将其标示为 @Transient

//  根据birth计算出age属性
@Transient
public int getAge() {
return getYear(new Date()) – getYear(birth);
}

@Temporal 时间格式

当我们使用到 java.util 包中的时间日期类型,则需要此注释来说明转化成 java.util 包中的类型。

注入数据库的类型有三种:

TemporalType.DATE(2008-08-08)
TemporalType.TIME(20:00:00)
TemporalType.TIMESTAMP(2008-08-08 20:00:00.000000001)

@Enumerated 映射枚举

使用此注解映射枚举字段,以 String 类型存入数据库

注入数据库的类型有两种:

  • EnumType.ORDINAL(Interger)
  • EnumType.STRING(String)

@Embedded 和 @Embeddable

@Embedded 将几个字段组合成一个类,并作为整个Entity的一个属性.

例如 User 包括 idnamecitystreetzip 属性,我们希望 citystreetzip 属性映射为 Address 对象。这样 User 对象将具有 idnameaddress 这三个属性。 Address 类必须定义为 @Embeddable

示例:

@Embeddable
public class Address {city,street,zip}

@Entity
public class User {
@Embedded
public Address getAddress() {
}
}

定义关系类型

简单地使用原始字段持久化对象只是方程式的一半。JPA 还具有管理彼此相关实体的能力。在表和对象中都有四种实体关系:

  • 一对多
  • 多对一
  • 许多一对多
  • 一对一

每种类型的关系描述了实体与其他实体的关系。例如,Musician 实体可以与由诸如 List 或 Set 的集合表示的实体具有一对多的关系。

public class Musician {

@OneToMany
@JoinColumn(name="musicianId")
private List<Performance> performances = new ArrayList<Performance>();
//...
}

需要注意的一点是 @JoinColumn 告诉 JPA Performance 表上的哪一列将映射到 Musician 实体。每个 performance 都将与单个 Musician 关联,该列由此列跟踪。

当 JPA 将一个 Musician 或一个 Performance 加载到数据库中时,它将使用此信息重新构建对象图。